package jku.mobcomp;

import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

import jku.mobcomp.helper.Base64;
import jku.mobcomp.helper.Base64Utils;
import jku.mobcomp.helper.Const;
import jku.mobcomp.helper.SphericMath;
import jku.mobcomp.helper.SphericPoint;
import jku.mobcomp.helper.Utility;

import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobInfoFactory;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.files.AppEngineFile;
import com.google.appengine.api.files.FileReadChannel;
import com.google.appengine.api.files.FileService;
import com.google.appengine.api.files.FileServiceFactory;
import com.google.appengine.api.files.FileWriteChannel;
import com.google.appengine.api.files.FinalizationException;
import com.google.appengine.api.files.LockException;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesService;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Transform;
/**
 * This class handles CRUD operations related to Boulder entity.
 * 
 *
 */

public class Boulder {

	  static Logger logger = Logger.getLogger(ImageServlet.class.getCanonicalName());
	  
  /**
   * Create or update Boulder for a particular country. Country entity has one to many
   * relation-ship with Boulder entity
   * 
   * @param name
   *          : name
   * @param description
   *          : description
   * @param lat
   *          : Latitude
   * @param lon
   *          : Longitude
   * @param grade	
   *          : grade
   * @param country
   *          : country name for which the Boulder is created.
   */
  public static Entity createOrUpdateBoulder( String keyId, String name, 
		  String description, String lat, String lon, String grade, 
		  String line, String countryId, String user,
		  String imageCleanBase64, String imageLineBase64, String imageType, 
		  BlobKey blobKeyBinaryUpload, boolean blobKeyIsLine) {
	  Double latDbl = Utility.convertStringToDouble(lat);
	  Double lonDbl = Utility.convertStringToDouble(lon);
	  Blob descriptionBlob = null;
	  if(description != null){
		  try {
			descriptionBlob = new Blob(description.getBytes(Const.CHARSET_UTF8) );
		  } catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		  }
	  }
	  Blob lineBlob = null;
	  if(line != null){
		  try {
			lineBlob = new Blob(line.getBytes(Const.CHARSET_UTF8) );
		  } catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		  }
	  }
	  // Integer lineIntArr[] = new Integer[] {1,2,7,8,98,99};   
	  // String lineString = lineIntArr.toString();
	  
	  // user imageBase64 or binaryData (when received)
	  byte[] imageCleanDataFull = null;
	  String imageCleanKeyFull = null;
	  byte[] imageLineDataFull = null;
	  String imageLineKeyFull = null;
	  if (blobKeyBinaryUpload == null){
		  imageCleanDataFull = convertImageBase64toBinary( imageCleanBase64);
		  imageCleanKeyFull = writeDataToBlobstore(imageCleanDataFull, imageType);
		  imageLineDataFull = convertImageBase64toBinary( imageLineBase64);
		  imageLineKeyFull = writeDataToBlobstore(imageLineDataFull, imageType);
	  } else {
		  FileService fileService = FileServiceFactory.getFileService();
		  AppEngineFile file = fileService.getBlobFile(blobKeyBinaryUpload);
		  if (!blobKeyIsLine) {
			  imageCleanKeyFull = file.getFullPath();
			  imageCleanDataFull = getImageFromPath(imageCleanKeyFull);
		  } else  {
			  imageLineKeyFull = file.getFullPath();
			  imageLineDataFull = getImageFromPath(imageLineKeyFull);
	  	  }
		  BlobInfo blobInfo = new BlobInfoFactory().loadBlobInfo(blobKeyBinaryUpload);
		  String contentType = blobInfo.getContentType();  // z.B image/png
		  if (contentType != null){
			  int indexSlash = contentType.indexOf("/");
			  if (indexSlash >= 0  && indexSlash<(contentType.length()-1) ){
				  imageType = contentType.substring(indexSlash+1);
			  }
		  }
	  }
	  
	  byte[] imageCleanDataLarge = reduzeImageSize( imageCleanDataFull, Const.IMAGESIZE_large);
	  byte[] imageCleanDataMedium = reduzeImageSize( imageCleanDataFull, Const.IMAGESIZE_medium);
	  byte[] imageCleanDataSmall = reduzeImageSize( imageCleanDataFull, Const.IMAGESIZE_small);
	  byte[] imageLineDataLarge = reduzeImageSize( imageLineDataFull, Const.IMAGESIZE_large);
	  byte[] imageLineDataMedium = reduzeImageSize( imageLineDataFull, Const.IMAGESIZE_medium);
	  byte[] imageLineDataSmall = reduzeImageSize( imageLineDataFull, Const.IMAGESIZE_small);
	  // write to Blobstore
	  String imageCleanKeyLarge = writeDataToBlobstore(imageCleanDataLarge, imageType);
	  String imageCleanKeyMedium = writeDataToBlobstore(imageCleanDataMedium, imageType);
	  String imageCleanKeySmall = writeDataToBlobstore(imageCleanDataSmall, imageType);
	  String imageLineKeyLarge = writeDataToBlobstore(imageLineDataLarge, imageType);
	  String imageLineKeyMedium = writeDataToBlobstore(imageLineDataMedium, imageType);
	  String imageLineKeySmall = writeDataToBlobstore(imageLineDataSmall, imageType);

	  Long keyIdLng = Utility.convertStringToLong(keyId);
	  Entity boulder = getSingleBoulderById(keyIdLng);
	  if(boulder == null){
		  boulder = new Entity(Const.ENTITY_Boulder);  // key id is created automatically (Long)
		  boulder.setProperty(Const.PROP_name, name);
		  boulder.setProperty(Const.PROP_description, descriptionBlob);
		  boulder.setProperty(Const.PROP_grade, grade);
		  boulder.setProperty(Const.PROP_lat, latDbl);
		  boulder.setProperty(Const.PROP_lon, lonDbl);
		  boulder.setProperty(Const.PROP_line, lineBlob);
		  boulder.setProperty(Const.PROP_countryID, countryId);
		  boulder.setProperty(Const.PROP_user, user);  
		  boulder.setProperty(Const.PROP_imageCleanKeyFull, imageCleanKeyFull);
		  boulder.setProperty(Const.PROP_imageCleanKeyLarge, imageCleanKeyLarge);
		  boulder.setProperty(Const.PROP_imageCleanKeyMedium, imageCleanKeyMedium);
		  boulder.setProperty(Const.PROP_imageCleanKeySmall, imageCleanKeySmall);
		  boulder.setProperty(Const.PROP_imageLineKeyFull, imageLineKeyFull);
		  boulder.setProperty(Const.PROP_imageLineKeyLarge, imageLineKeyLarge);
		  boulder.setProperty(Const.PROP_imageLineKeyMedium, imageLineKeyMedium);
		  boulder.setProperty(Const.PROP_imageLineKeySmall, imageLineKeySmall);
		  boulder.setProperty(Const.PROP_imageType, imageType);
	  } else{
	    if (name != null && !"".equals(name)) {
			  boulder.setProperty(Const.PROP_name, name);
	    }    
	    if (description != null && !"".equals(description)) {
			  boulder.setProperty(Const.PROP_description, descriptionBlob);
	    }    
	    if (grade != null && !"".equals(grade)) {
			  boulder.setProperty(Const.PROP_grade, grade);
	    }           
	    if (latDbl != null ) {
			  boulder.setProperty(Const.PROP_lat, latDbl);
	    }           
	    if (lonDbl != null) {
			  boulder.setProperty(Const.PROP_lon, lonDbl);
	    }         
	    if (line != null) {
			  boulder.setProperty(Const.PROP_line, lineBlob);
	    }            
	    if (countryId != null ) {
			  boulder.setProperty(Const.PROP_countryID, countryId);
	    }                          
	    if (user != null ) {
			  boulder.setProperty(Const.PROP_user, user);
	    }                        
	    if (imageCleanKeyFull != null ) {
			  boulder.setProperty(Const.PROP_imageCleanKeyFull, imageCleanKeyFull);
	    }                      
	    if (imageCleanKeySmall != null ) {
			  boulder.setProperty(Const.PROP_imageCleanKeySmall, imageCleanKeySmall);
	    }                      
	    if (imageCleanKeyMedium != null ) {
			  boulder.setProperty(Const.PROP_imageCleanKeyMedium, imageCleanKeyMedium);
	    }                      
	    if (imageCleanKeyLarge != null ) {
			  boulder.setProperty(Const.PROP_imageCleanKeyLarge, imageCleanKeyLarge);
	    }                         
	    if (imageLineKeyFull != null ) {
			  boulder.setProperty(Const.PROP_imageLineKeyFull, imageLineKeyFull);
	    }                      
	    if (imageLineKeySmall != null ) {
			  boulder.setProperty(Const.PROP_imageLineKeySmall, imageLineKeySmall);
	    }                      
	    if (imageLineKeyMedium != null ) {
			  boulder.setProperty(Const.PROP_imageLineKeyMedium, imageLineKeyMedium);
	    }                      
	    if (imageLineKeyLarge != null ) {
			  boulder.setProperty(Const.PROP_imageLineKeyLarge, imageLineKeyLarge);
	    }                  
	    if (imageType != null ) {
			  boulder.setProperty(Const.PROP_imageType, imageType);
	    }  
	  }
	  boulder.setProperty(Const.PROP_date, new Date() );
	  Utility.persistEntity(boulder);
	  
	  return boulder;
  }


/**
   * get All the boulders in the list
   * 
   * @return all the boulders
   */
  public static Iterable<Entity> getAllBoulders() {
  	Iterable<Entity> entities = Utility.listEntities(Const.ENTITY_Boulder, null, null);
  	return entities;
  }

  /**
   * Get the boulder by name
   * @param boulderName 
   *          : boulder name
   * @return Boulder Entities
   */
  public static Iterable<Entity> getBouldersByName(String boulderName) {
  	Iterable<Entity> entities = Utility.listEntities(Const.ENTITY_Boulder, Const.PROP_name, boulderName);
  	return entities;
  }
  
  /**
   * Get the boulder by name,description  -- search with like
   * @param string 
   * @return Boulder Entities
   */
  public static Iterable<Entity> getBouldersByLike(String likeSearch) {

	  LinkedList<Entity> results = new LinkedList<Entity>();
	  Iterable<Entity> boulders = getAllBoulders();

	  int countResult = 0;
	  if (likeSearch!=null && likeSearch.length()>0 && boulders!=null){
		  Iterator<Entity> it = boulders.iterator();
		  String lowerSearch = likeSearch.toLowerCase();
		  while (it.hasNext()){
			  Entity boulder = it.next();
			  String name = (String) boulder.getProperty(Const.PROP_name);
			  Blob descriptionBlob = (Blob) boulder.getProperty(Const.PROP_description);
			  String description = null;
			  if (descriptionBlob!=null )  {
				  try {
					description = new String( descriptionBlob.getBytes(), Const.CHARSET_UTF8 );
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		  	  }	  
			  int startName = -1;
			  int startDescription = -1;
			  if (name!=null && name.length()>0 )  {
				  String lowerName = name.toLowerCase();
				  startName = lowerName.indexOf(lowerSearch);
		  	  }	  
			  if (description!=null && description.length()>0 )  {
				  String lowerDescription = description.toLowerCase();
				  startDescription = lowerDescription.indexOf(lowerSearch);
		  	  }	  
			  if (startName >=0 || startDescription >=0 )  {
				  results.add(boulder);
				  countResult++;
		  	  }	  
		  }	  
	  }	  
	  if (countResult==0 ){
		  results = null; 
	  }	  
	  return results; 
  }
  
  /**
   * Get the boulder by grade
   * 
   * @param grade
   * @return Boulder Entities
   */
  public static Iterable<Entity> getBouldersByGrade(String grade) {
  	Iterable<Entity> entities = Utility.listEntities(Const.ENTITY_Boulder, Const.PROP_grade, grade);
  	return entities;
  }

  /**
   * Get all the boulder for a country
   * 
   * @param countryName
   *          : country name
   * @return: all items of type country
   */
  public static Iterable<Entity> getBouldersForCountry(String countryName) {
	  Iterable<Entity> countrys = Country.getCountryByName(countryName);
	if (countrys!=null) {  
		  Iterator<Entity> it = countrys.iterator();
		  if (it.hasNext()){
			  Entity country = it.next();
			  Iterable<Entity> boulders = Boulder.getBouldersByCountryId( String.valueOf(country.getKey().getId()));	
			  return boulders; 
		  }	    
	}
	return null;
  }
  
  /**
   * Get the Boulder by countryId
   * 
   * @param countryId
   * @return Boulder Entities
   */
  public static Iterable<Entity> getBouldersByCountryId(String countryId) {
  	Iterable<Entity> entities = Utility.listEntities(Const.ENTITY_Boulder, Const.PROP_countryID, countryId);
  	return entities;
  }

  /**
   * get Boulder with name
   * @param name: get name
   * @return  boulder entity
   */
  public static Entity getSingleBoulder(String name) {
	  Query query = new Query(Const.ENTITY_Boulder);
	  query.addFilter(Const.PROP_name, FilterOperator.EQUAL, name);
	  List<Entity> results = Utility.getDatastoreServiceInstance().prepare(query).asList(FetchOptions.Builder.withDefaults());
	  if (!results.isEmpty()) {
	    return (Entity)results.remove(0);
	  }
	  return null;
  }
  
  /**
   * get Boulder with keyId
   * @param keyId: (Long)
   * @return  boulder entity
   */
  public static Entity getSingleBoulderById(Long keyId) {
	  if (keyId!=null && !keyId.toString().equals("") && !keyId.toString().equals("0")) {
		  Key keySearch = KeyFactory.createKey(Const.ENTITY_Boulder, keyId);
		  return Utility.findEntity(keySearch); 
	  }
	  return null;
  }
  
  /**
   * Delete boulder entity
   * @param boulderKey: boulder to be deleted
   * @return status string
   */
  public static String deleteBoulder(String boulderKeyId){ 
	  Long keyIdLng = Utility.convertStringToLong(boulderKeyId);
	  Entity boulderEntity = getSingleBoulderById(keyIdLng); 
	  Iterable<Entity> ratings = Rating.getRatingsByBoulderId( boulderKeyId);	  
	  Iterator<Entity> it = ratings.iterator();
	  if (it.hasNext()){
	      return "Cannot delete, as there are Ratings associated with this Boulder.";	      
	  }	  
	  if(boulderEntity != null){
		  Key key = boulderEntity.getKey();
		  Utility.deleteEntity(key);
		  return("Boulder " + key + " deleted successfully.");
		} else
	  return("Boulder not found");  
  }
  
  // append some Properties which are calculate from other existing properties,
  // the Elements of the Iterable entities are modified (adding new properties) 
  // selected Elements are put into the result-Iterable
  // Elements are persisted after adding all the calculated values
	public static Iterable<Entity> appendBoulderResultProperties(Iterable<Entity> boulders, 
			String posLat, String posLon, String maxDistance, String userForCheckRatingDone) {
		LinkedList<Entity> entitiesResult= new LinkedList<Entity>();
			
		// user location
		SphericPoint point1 = null;
	//  Double posLatDbl = Double.parseDouble("48.804642"); //Gebharts
	//  Double posLonDbl = Double.parseDouble("15.125672"); //Gebharts
		Double posLatDbl = Utility.convertStringToDouble(posLat);
		Double posLonDbl = Utility.convertStringToDouble(posLon);
		Double maxDistanceDbl = Utility.convertStringToDouble(maxDistance);
		if (posLatDbl!=null && posLonDbl!=null) {
			point1 = new SphericPoint( posLonDbl, posLatDbl); // lon/lat!Folge
		}
		
	  	for (Entity boulder : boulders) { 
	  		Entity boulderResult = new Entity(Const.ENTITY_BoulderResult, boulder.getKey() );  // same key
	  		// The result Entity gets another key, store the origin key of the boulder as property 
	  		boulderResult.setProperty( Const.PROP_keyId, Long.toString(boulder.getKey().getId()) );
			
	  		Double latDbl = null;
	  		Double lonDbl = null;
	  		int countGrade0 =0;
	  		int countGrade1 =0;
	  		int countGrade2 =0;
	  		int countRatings=0;
	  		int sumFun=0;
			Boolean ratingDone = Boolean.FALSE; 
	  		Map<String, Object> properties = boulder.getProperties();
	    	  
		  	for (String key : properties.keySet()) {
			  	Object value = properties.get(key) ;
		  		// copy only desired properties to Result-Entity
				if ( !key.equals(Const.PROP_countryID) && 
					 !key.equals(Const.PROP_imageCleanKeyFull)&& 
					 !key.equals(Const.PROP_imageCleanKeyLarge)&& 
					 !key.equals(Const.PROP_imageCleanKeyMedium)&& 
					 !key.equals(Const.PROP_imageCleanKeySmall)&& 
					 !key.equals(Const.PROP_imageLineKeyFull)&& 
					 !key.equals(Const.PROP_imageLineKeyLarge)&& 
					 !key.equals(Const.PROP_imageLineKeyMedium)&& 
					 !key.equals(Const.PROP_imageLineKeySmall) ){
					boulderResult.setProperty(key, value);					  
				}
		  		
		  		// fetch the value of some properties for calculation some results
				if (key.equals(Const.PROP_lat)){
					latDbl = (Double) value;
				}
				if (key.equals(Const.PROP_lon)){
					lonDbl = (Double) value;
				}
		  	}
		  	// find boulder-location and calculate distance
			SphericPoint point2 = null;
			if (latDbl!=null && lonDbl!=null ){  // lat/lon is in Entity available
				point2 = new SphericPoint( lonDbl, latDbl);
			}
			Double distanceDbl = null;
			if (point1!=null && point2!=null ){  // two Points available
				distanceDbl = new Double( SphericMath.earthDistance(point1, point2) );
			}
			boulderResult.setProperty(Const.PROP_distance, distanceDbl);
		  	// find Ratings for this boulder
			Iterable<Entity> ratings = Rating.getRatingsByBoulderId( String.valueOf(boulder.getKey().getId()) );	  
			  if (ratings!=null){ 
				  for (Entity rating : ratings) {
						Long ratingGrade = (Long) rating.getProperty(Const.PROP_ratingGrade);
						Long ratingFun = (Long) rating.getProperty(Const.PROP_ratingFun);
						if (ratingGrade!=null){
							if (ratingGrade.longValue()==0){
								countGrade0++;
							}
							if (ratingGrade.longValue()==1){
								countGrade1++;
							}
							if (ratingGrade.longValue()==2){
								countGrade2++;
							}
						}
						if (ratingFun!=null){
							countRatings++;
							sumFun += ratingFun;
						}
						String userRated = (String) rating.getProperty(Const.PROP_user);
						if(userRated != null && userForCheckRatingDone!=null && userRated.equals(userForCheckRatingDone) ){
							ratingDone = Boolean.TRUE;
						}
				  }		
			  }	
			Double avgFun = 0.0;  // init with 0 (never null)
			if (countRatings!=0 ) {
				double countFunDbl = countRatings;
				double sumFunDbl = sumFun;
				double avgFunDbl = sumFunDbl/countFunDbl;
				avgFun =  Double.valueOf(avgFunDbl );
			}
			boulderResult.setProperty(Const.PROP_countGrade0, Integer.valueOf(countGrade0) );
			boulderResult.setProperty(Const.PROP_countGrade1, Integer.valueOf(countGrade1) );
			boulderResult.setProperty(Const.PROP_countGrade2, Integer.valueOf(countGrade2) );
			boulderResult.setProperty(Const.PROP_avgFun, avgFun);
			boulderResult.setProperty(Const.PROP_countRatings, countRatings);
			boulderResult.setProperty(Const.PROP_ratingDone, ratingDone);

			  //set image Path for receiving Images from Server
			  String imagePathEnding = "_" + boulder.getKey().getId() 
					  				+ "." + boulder.getProperty(Const.PROP_imageType);
			  String imageCleanPathFull = Const.IMAGECLEANPATHSTART + Const.IMAGESIZE_full  + imagePathEnding;
			  String imageCleanPathLarge= Const.IMAGECLEANPATHSTART + Const.IMAGESIZE_large + imagePathEnding;
			  String imageCleanPathMedium= Const.IMAGECLEANPATHSTART + Const.IMAGESIZE_medium + imagePathEnding;
			  String imageCleanPathSmall= Const.IMAGECLEANPATHSTART + Const.IMAGESIZE_small + imagePathEnding;
			  String imageLinePathFull = Const.IMAGELINEPATHSTART + Const.IMAGESIZE_full  + imagePathEnding;
			  String imageLinePathLarge= Const.IMAGELINEPATHSTART + Const.IMAGESIZE_large + imagePathEnding;
			  String imageLinePathMedium= Const.IMAGELINEPATHSTART + Const.IMAGESIZE_medium + imagePathEnding;
			  String imageLinePathSmall= Const.IMAGELINEPATHSTART + Const.IMAGESIZE_small + imagePathEnding;
			  if (boulder.getProperty(Const.PROP_imageCleanKeyFull)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageCleanPathFull, imageCleanPathFull); 
			  }
			  if (boulder.getProperty(Const.PROP_imageCleanKeyLarge)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageCleanPathLarge, imageCleanPathLarge); 
			  }
			  if (boulder.getProperty(Const.PROP_imageCleanKeyMedium)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageCleanPathMedium, imageCleanPathMedium); 
			  }
			  if (boulder.getProperty(Const.PROP_imageCleanKeySmall)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageCleanPathSmall, imageCleanPathSmall); 
			  }
			  if (boulder.getProperty(Const.PROP_imageLineKeyFull)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageLinePathFull, imageLinePathFull); 
			  }
			  if (boulder.getProperty(Const.PROP_imageLineKeyLarge)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageLinePathLarge, imageLinePathLarge); 
			  }
			  if (boulder.getProperty(Const.PROP_imageLineKeyMedium)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageLinePathMedium, imageLinePathMedium); 
			  }
			  if (boulder.getProperty(Const.PROP_imageLineKeySmall)!=null ) {
				  boulderResult.setProperty(Const.PROP_imageLinePathSmall, imageLinePathSmall); 
			  }

			    logger.log(Level.INFO, "imageLinePathFull:  " + imageLinePathFull);
			  
		  	// persist Data
			// do not persist because of limited data-quota 
			// persistance is not necessary: boulderResult is only used for building the json-result
			    //		Utility.persistEntity(boulderResult);
	
			// dont add Boulder, when distance is to large.
			if (distanceDbl==null || maxDistanceDbl==null 
					|| distanceDbl.doubleValue() <= maxDistanceDbl.doubleValue() ){  
				entitiesResult.add(boulderResult);
			}
	    }
		
		return entitiesResult;
	}
	
	  /**
	   * Convert a StringBase64 to a Blob
	   * @param imageBase64
	   * @return byte[]
	   */
	public static byte[] convertImageBase64toBinary(String imageBase64) {
		// convert base64 to binary
		byte[] imageDataByte = null;
		if (imageBase64!=null && !imageBase64.isEmpty() ) {
			
			 // problem with large images - use another decoder!
			// decode = from base64 to binary
			try {
				imageDataByte = Base64Utils.decode(imageBase64.getBytes() );
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} 
			
			//imageDataByte = Base64.decode(imageBase64.getBytes() );   
			
		}
		return imageDataByte;
	}  	 

	  /**
	   * reduze the size of an image
	   * @param byte[]-original image
	   * @return byte[]
	   */
	public static byte[] reduzeImageSize(byte[] imageData, String imageSize) {
		byte[] imageDataResized = null;
	    ImagesService imagesService = ImagesServiceFactory.getImagesService();

		Logger logger = Logger.getLogger(ImageServlet.class.getCanonicalName());
		
	    int newHeight = 0;
	    int newWidth = 0;
	    int maxBytes = 0;
	    if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_large)) {
			newHeight= Const.IMAGESIZE_largeHeight;
			newWidth= Const.IMAGESIZE_largeWidth;
		}  
	    if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_medium)) {
			newHeight= Const.IMAGESIZE_mediumHeight;
			newWidth= Const.IMAGESIZE_mediumWidth;
		}  
	    if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_small)) {
			newHeight= Const.IMAGESIZE_smallHeight;
			newWidth= Const.IMAGESIZE_smallWidth;
		}  

	    logger.log(Level.INFO, "change ImageSize to  " + newHeight + "/" + newWidth);
	    
		// convert imageBlob to an Image (use Factory) and Resize image and put it in a Blob
		if (imageData!=null && newHeight>0 && newWidth>0) {	 
		    Image image = ImagesServiceFactory.makeImage(imageData);
		    // with crop - center is 0.5 / 0.5
		    Transform transform = ImagesServiceFactory.makeResize( newWidth, newHeight, 0.5, 0.5);
		    Image imageResized = imagesService.applyTransform(transform, image);
		   imageDataResized = imageResized.getImageData();
		    // reduce the size of the image
//		    byte[] imageDataReducedQuality = imageDataResized;
//		    int qualityFactor = 95;
//		    while (imageDataReducedQuality.length>maxBytes && qualityFactor>0) {
//		    	BufferedImage img = ImageIO.read(arg0):
//			}  
		}  
		
		return imageDataResized;
	}  
	
	
	/**
	   * WriteDataToBlobstore
	   * @param byte[] imageDataByte
	   * @return String path
	 * @throws IOException 
	   */
	public static String writeDataToBlobstore(byte[] imageDataByte, String imageType)  {
		String path = null;
		Logger logger = Logger.getLogger(ImageServlet.class.getCanonicalName());
	    logger.log(Level.INFO, "start writeData to blobstore:  " + imageType);
		if (imageDataByte!=null ) {
			  // Get a file service
			  FileService fileService = FileServiceFactory.getFileService();
			    logger.log(Level.INFO, "start writeData to blobstore_3:  " );

			  // Create a new Blob file with mime-type "text/plain"
			  AppEngineFile file = null;
			try {
				file = fileService.createNewBlobFile("text/plain");
			    logger.log(Level.INFO, "start writeData to blobstore_4:  " );
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}

			  // Open a channel to write to it
			  boolean lock = true;
			  FileWriteChannel writeChannel = null;
			try {
				writeChannel = fileService.openWriteChannel(file, lock);
			    logger.log(Level.INFO, "start writeData to blobstore_5:  " );
			} catch (FileNotFoundException e) {
			    logger.log(Level.INFO, "start writeData to blobstore_5 filenotfound:  "  +e.toString()  );
				e.printStackTrace();
			} catch (FinalizationException e) {
			    logger.log(Level.INFO, "start writeData to blobstore_5 finalizationException:  "  +e.toString()  );
						e.printStackTrace();
			} catch (LockException e) {
			    logger.log(Level.INFO, "start writeData to blobstore_5 LockException:  "  +e.toString()  );
						e.printStackTrace();
			} catch (IOException e) {
			    logger.log(Level.INFO, "start writeData to blobstore_5 ioException:  "  +e.toString()  );
					e.printStackTrace();
			}

			  // This time we write to the channel directly
			 try {
				writeChannel.write(ByteBuffer.wrap(imageDataByte));
			    logger.log(Level.INFO, "start writeData to blobstore_6:  " );
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			  // Now finalize
			  try {
				writeChannel.closeFinally();
			    logger.log(Level.INFO, "start writeData to blobstore_7:  " );
			} catch (IllegalStateException e) {
			    logger.log(Level.INFO, "start writeData to blobstore_7 illegalStateException:  "  +e.toString() );
						e.printStackTrace();
			} catch (IOException e) {
			    logger.log(Level.INFO, "start writeData to blobstore_7 ioException:  " +e.toString() );
						e.printStackTrace();
			}
			  path = file.getFullPath();
			    logger.log(Level.INFO, "start writeData to blobstore  path:  " + path );


		}
		return path;
	}  

	
	  /**
	   * retrieve Blog from Datastore and create an image
	   * @param KeyId of the boulder :boulderKeyId
	   * @return byte[] image
	 * @throws IOException 
	 * @throws LockException 
	 * @throws FileNotFoundException 
	   */
	public static byte[] getImage(String boulderKeyId, String imageSize, boolean imageIsLine) {
		byte[] imageDataByte = null;
		 Logger logger = Logger.getLogger(ImageServlet.class.getCanonicalName());

		    logger.log(Level.INFO, "getImage-key: " + boulderKeyId +  "  size: " + imageSize);
		Long keyIdLng = Utility.convertStringToLong(boulderKeyId);
	    Entity boulderEntity = getSingleBoulderById(keyIdLng); 
	    // set Image size: default is a full-size image
	    String imageKey = null;
		if ( !imageIsLine ) {
		    imageKey = Const.PROP_imageCleanKeyFull;
			if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_large)) {
				imageKey = Const.PROP_imageCleanKeyLarge;
			}  
			if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_medium)) {
				imageKey = Const.PROP_imageCleanKeyMedium;
			}  
			if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_small)) {
				imageKey = Const.PROP_imageCleanKeySmall;
			}  
		} else {
		    imageKey = Const.PROP_imageLineKeyFull;
			if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_large)) {
				imageKey = Const.PROP_imageLineKeyLarge;
			}  
			if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_medium)) {
				imageKey = Const.PROP_imageLineKeyMedium;
			}  
			if (imageSize!=null && imageSize.equals(Const.IMAGESIZE_small)) {
				imageKey = Const.PROP_imageLineKeySmall;
			}  
		}
	    
		// fetch data from blobstore
		String path = (String) boulderEntity.getProperty(imageKey);
		imageDataByte = getImageFromPath(path);

    	
		return imageDataByte;
	}
	
	/**
	 * calculate the small/large images once again with actually size and persist them
	 * @param KeyId of the boulder :boulderKeyId
	 * @return String imageType
	 */
	public static byte[] getImageFromPath(String path) {
		byte[] imageDataByte = null;
		 Logger logger = Logger.getLogger(ImageServlet.class.getCanonicalName());
		if (path!= null) {
		    // Read from the file using the file API
			logger.log(Level.INFO, "fetch image from blobstore-path: " + path );
			
			// because of fetching data from blog is limited to about 1MB of Data
			// the result is spittet into little byte-arrays and reassembled
			// (in eclipse-locally this would not be necessary, but on real google-app-engine 
			// you can only fetch these small parts of the resul)
			if (path!= null) {
				AppEngineFile file = new AppEngineFile(path);
				FileService fileService = FileServiceFactory.getFileService();
				BlobKey blobKey = fileService.getBlobKey(file);
				logger.log(Level.INFO, "Blob KEy: " + blobKey.toString() );
				BlobstoreService blobStoreService = BlobstoreServiceFactory.getBlobstoreService();
				boolean repeat=true;
				imageDataByte = new byte[0];  // empty Byte-Array
				long lowerLimit= 0;
				long upperLimit= blobStoreService.MAX_BLOB_FETCH_SIZE -1;
				while (repeat) {
					logger.log(Level.INFO, "lowerLimit for Fetch: " + lowerLimit );
					logger.log(Level.INFO, "upperLimit for Fetch: " + upperLimit );
					byte[] fetchByte = blobStoreService.fetchData(blobKey, lowerLimit, upperLimit);
					// arraycopy-parms: src, srcPos, dest, destPos, length
					byte[] combinedByte = new byte[imageDataByte.length + fetchByte.length];
					System.arraycopy(imageDataByte, 0, combinedByte, 0, imageDataByte.length);
					System.arraycopy(fetchByte, 0, combinedByte, imageDataByte.length, fetchByte.length);
					imageDataByte = combinedByte;
					logger.log(Level.INFO, "byte-length - fetch: " + fetchByte.length );
					logger.log(Level.INFO, "byte-length - total: " + imageDataByte.length );
					long maxBytes = upperLimit-lowerLimit+1;  // attention: +1
					if (fetchByte.length == maxBytes) {
						lowerLimit += BlobstoreService.MAX_BLOB_FETCH_SIZE;
						upperLimit += BlobstoreService.MAX_BLOB_FETCH_SIZE;
					} 	
					else {
						repeat=false;
					}  
				 
				}  
				logger.log(Level.INFO, "byte-length: " + imageDataByte.length );
			}  
		}
		return imageDataByte;
	}
	
	
	/**
	 * calculate the small/large images once again with actually size and persist them
	 * @param KeyId of the boulder :boulderKeyId
	 * @return String imageType
	 */
	public static void resizeImages(Iterable<Entity> boulders) {

		  if (boulders!=null){
			  Iterator<Entity> it = boulders.iterator();
			  while (it.hasNext()){
				  Entity boulder = it.next();
				  long  keyId = boulder.getKey().getId();
				  String imageType = (String) boulder.getProperty(Const.PROP_imageType);

				  // clean image
				  boolean imageIsLine = false;
				  byte[] imageCleanDataFull = getImage(String.valueOf(keyId), Const.IMAGESIZE_full, imageIsLine);
				  byte[] imageCleanDataLarge = reduzeImageSize( imageCleanDataFull, Const.IMAGESIZE_large);
				  byte[] imageCleanDataMedium = reduzeImageSize( imageCleanDataFull, Const.IMAGESIZE_medium);
				  byte[] imageCleanDataSmall = reduzeImageSize( imageCleanDataFull, Const.IMAGESIZE_small);
				 
				  String imageCleanKeyLarge = writeDataToBlobstore(imageCleanDataLarge, imageType);
				  String imageCleanKeyMedium = writeDataToBlobstore(imageCleanDataMedium, imageType);
				  String imageCleanKeySmall = writeDataToBlobstore(imageCleanDataSmall, imageType);

				  if (imageCleanKeySmall != null ) {
					  boulder.setProperty(Const.PROP_imageCleanKeySmall, imageCleanKeySmall);
				  }                  
				  if (imageCleanKeyMedium != null ) {
					  boulder.setProperty(Const.PROP_imageCleanKeyMedium, imageCleanKeyMedium);
				  }                          
				  if (imageCleanKeyLarge != null ) {
					  boulder.setProperty(Const.PROP_imageCleanKeyLarge, imageCleanKeyLarge);
				  }  
				  
				  // line image
				  imageIsLine = true;
				  byte[] imageLineDataFull = getImage(String.valueOf(keyId), Const.IMAGESIZE_full, imageIsLine);
				  byte[] imageLineDataLarge = reduzeImageSize( imageLineDataFull, Const.IMAGESIZE_large);
				  byte[] imageLineDataMedium = reduzeImageSize( imageLineDataFull, Const.IMAGESIZE_medium);
				  byte[] imageLineDataSmall = reduzeImageSize( imageLineDataFull, Const.IMAGESIZE_small);
				 
				  String imageLineKeyLarge = writeDataToBlobstore(imageLineDataLarge, imageType);
				  String imageLineKeyMedium = writeDataToBlobstore(imageLineDataMedium, imageType);
				  String imageLineKeySmall = writeDataToBlobstore(imageLineDataSmall, imageType);

				  if (imageLineKeySmall != null ) {
					  boulder.setProperty(Const.PROP_imageLineKeySmall, imageLineKeySmall);
				  }                  
				  if (imageLineKeyMedium != null ) {
					  boulder.setProperty(Const.PROP_imageLineKeyMedium, imageLineKeyMedium);
				  }                          
				  if (imageLineKeyLarge != null ) {
					  boulder.setProperty(Const.PROP_imageLineKeyLarge, imageLineKeyLarge);
				  } 
				    
					// persist the changed image-keys  
				    Utility.persistEntity(boulder);
			  }	  
		  }	  
	}
	
	
	/**
	 * retrieve the Type of the Image
	 * @param KeyId of the boulder :boulderKeyId
	 * @return String imageType
	 */
	public static String getImageType(String boulderKeyId) {
		Long keyIdLng = Utility.convertStringToLong(boulderKeyId);
		Entity boulderEntity = getSingleBoulderById(keyIdLng); 
		String imageType = null;
		if (boulderEntity!=null) {
			imageType = (String) boulderEntity.getProperty(Const.PROP_imageType);
		}
		return imageType;
	}

	

	/**
	 * retrieve values from the PathInfo (e.g. /images/full_1234.jpg)
	 * @param pathInfo 
	 * @param pathInfo 
	 * @return String value
	 */
	public static String extractImageSizeFromPathInfo(String pathInfo) {
		String value = null;
		// keyword can be somewhere in the path
		if (pathInfo!=null && !pathInfo.isEmpty() ) {
			String path = pathInfo.toLowerCase();
			int posSize = 0;
			posSize = path.indexOf(Const.IMAGESIZE_full);
			if (posSize >= 0)  {
				value = Const.IMAGESIZE_full;
			}
			posSize = path.indexOf(Const.IMAGESIZE_large);
			if (posSize >= 0)  {
				value = Const.IMAGESIZE_large;
			}
			posSize = path.indexOf(Const.IMAGESIZE_medium);
			if (posSize >= 0)  {
				value = Const.IMAGESIZE_medium;
			}
			posSize = path.indexOf(Const.IMAGESIZE_small);
			if (posSize >= 0)  {
				value = Const.IMAGESIZE_small;
			}
		}
		return value;
	}

	public static String extractKeyIdFromPathInfo(String pathInfo) {
		String value = null;
		// keyId - must begin after the underline-sign and ends before the dot
		if (pathInfo!=null && !pathInfo.isEmpty() ) {
			String path = pathInfo.toLowerCase();
			int posUnderline = path.indexOf("_");
			int posDot = path.indexOf(".");
			if (posUnderline >= 0 && posDot > posUnderline)  {
				value = path.substring(posUnderline+1, posDot);  // SearchValue starts at +1!
			}
		}
		return value;
	}

	public static boolean extractIsLineFromRequestUri(String requestUri) {
		boolean imageIsLine = false;
		// keyId - must begin after the underline-sign and ends before the dot
		if (requestUri!=null && !requestUri.isEmpty() ) {
			String path = requestUri.toLowerCase();
			String searchString = Const.IMAGELINEPATHSTART.toLowerCase() ;
			int posSearch = path.indexOf(searchString);
			if (posSearch >= 0 )  {
				imageIsLine = true;  
			}
		}
		return imageIsLine;
	}
	
}
